﻿using Microsoft.Phone.Info;
using Microsoft.Phone.Marketplace;
using Microsoft.Phone.Net.NetworkInformation;
using Microsoft.Phone.Shell;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Navigation;

namespace SMARTMobile.WinPhone
{
    /// <summary>
    /// Represent the based ViewModel for every phone page.
    /// </summary>
    public class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo, IWinPhoneViewModel
    {
        #region Fields

        private bool _hasErrors;
        private bool _isBusy;
        private string _busyText;
        private static bool? _allowLbs;
        protected IDictionary<string, string> Errors { get; set; }
        protected static readonly object SyncRoot = new object();


        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #endregion

        #region C'tor

        static ViewModelBase()
        {
            LicenseInformation license = new LicenseInformation();
            IsTrialVersion = license.IsTrial();
        }

        // needs to be public for serialization
        public ViewModelBase()
        {
            Errors = new Dictionary<string, string>();
        }
        #endregion

        #region Properties

        public static string DeviceID
        {
            get 
            { 
                byte[] id = (byte[])DeviceExtendedProperties.GetValue("DeviceUniqueId");
                return Convert.ToBase64String(id);
            }
        }

        protected PageNavigationService PageNavigation
        {
            get { return PageNavigationService.Current; }
        }

        protected DataSourceService DataSource
        {
            get { return DataSourceService.Current; }
        }

        protected LogService Log
        {
            get { return LogService.Current; }
        }

        protected bool IsFirstLaunch
        {
            get { return PhoneRootService.IsFirstLaunch; }
        }

        protected bool AllowLocationBasedService
        {
            get
            {
                lock (SyncRoot)
                {
                    if (_allowLbs.HasValue)
                    {
                        return _allowLbs.Value;
                    }
                    return IsolatedStorageSettings.ApplicationSettings.Contains("AllowLbs");
                }
            }
            set
            {
                lock (SyncRoot)
                {
                    _allowLbs = value; // gets around the latency in ApplicationSettings.Save()
                    if (value && !IsolatedStorageSettings.ApplicationSettings.Contains("AllowLbs"))
                    {
                        IsolatedStorageSettings.ApplicationSettings.Add("AllowLbs", true);
                    }
                    else if (IsolatedStorageSettings.ApplicationSettings.Contains("AllowLbs"))
                    {
                        IsolatedStorageSettings.ApplicationSettings.Remove("AllowLbs");
                    }
                    IsolatedStorageSettings.ApplicationSettings.Save();
                }
            }
        }

        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                _isBusy = value; 
                BeginInvoke(() => RaisePropertyChanged("IsBusy"));
                if (!value) BusyText = string.Empty;
            }
        }

        public string BusyText
        {
            get { return _busyText; }
            set
            {
                _busyText = value; 
                BeginInvoke(() => RaisePropertyChanged("BusyText"));
            }
        }

        public static bool IsTrialVersion
        {
            get;
#if DEBUG
            set;
#else
            private set;
#endif
        }

        public bool IsNetworkAvailable
        {
            get { return NetworkInterface.GetIsNetworkAvailable(); }
        }

        public static bool IsInDesignTool
        {
            get { return DesignerProperties.IsInDesignTool; }
        }
        #endregion

        #region Methods

        protected void DoBackgroundWork(DoWorkEventHandler callback)
        {
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += callback;
            worker.RunWorkerCompleted += RunWorkerCompleted;
            worker.RunWorkerAsync(callback);
        }

        private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            DoWorkEventHandler handler = e.UserState as DoWorkEventHandler;
            worker.DoWork -= handler;
            worker.RunWorkerCompleted += RunWorkerCompleted;
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (null != PropertyChanged)
            {
                BeginInvoke(() => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
            }
        }

        protected void ShowMessage(string message)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show(message));
        }

        protected void BeginInvoke(Action a)
        {
            Deployment.Current.Dispatcher.BeginInvoke(a);
        }

        protected void BeginInvoke(Delegate d, params object[] args)
        {
            Deployment.Current.Dispatcher.BeginInvoke(d, args);
        }
        #endregion

        #region INotifyDataErrorInfo Members

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
        {
            return _hasErrors
                       ? (from e in Errors where e.Key == propertyName select e)
                       : null;
        }

        bool INotifyDataErrorInfo.HasErrors
        {
            get
            {
                return _hasErrors;
            }
        }
        #endregion

        #region IViewModel Members

        public string ViewName
        {
            get; set;
        }

        public virtual void Initialize(Uri uri)
        {
            TraceEntrance();
            DebugTrace(string.Format("ViewModelBase::Initialize({0})", uri));
            TraceExit();
        }

        public virtual void Reset()
        {
            TraceEntrance();
            TraceExit();
        }

        public virtual void PageLoaded(Uri uri)
        {
            TraceEntrance();
            DebugTrace(string.Format("ViewModelBase::PageLoaded({0})", uri));
            TraceExit();
        }

        public virtual void PageUnloaded(Uri uri)
        {
            TraceEntrance();
            DebugTrace(string.Format("ViewModelBase::PageUnloaded({0})", uri));
            TraceExit();
        }

        public virtual void OnBackKeyPress(CancelEventArgs args)
        {
            TraceEntrance();
            TraceExit();
        }

        public virtual void NavigatedTo(NavigationMode mode, Uri uri, IDictionary<string, string> queryString)
        {
            TraceEntrance();
            DebugTrace(string.Format("ViewModelBase::NavigatedTo(mode={0}, uri={1}, queryString.Count={2})", mode, uri, queryString.Count));
            TraceExit();
        }

        public virtual void NavigatedFrom(NavigationMode mode, Uri uri, IDictionary<string, string> queryString)
        {
            TraceEntrance();
            DebugTrace(string.Format("ViewModelBase::NavigatedFrom(mode={0}, uri={1}, queryString.Count={2})", mode, uri, queryString.Count));
            TraceExit();
        }

        public virtual void ApplicationBarStateChanged(bool isMenuVisible)
        {
            TraceEntrance();
            DebugTrace(string.Format("ViewModelBase::ApplicationBarStateChanged(isMenuVisible={0})", isMenuVisible));
            TraceExit();
        }

        public virtual void ApplicationBarButtonClick(IApplicationBarIconButton button)
        {
            TraceEntrance();
            DebugTrace(string.Format("ViewModelBase::ApplicationBarButtonClick({0})", button.Text));
            TraceExit();
        }

        public virtual void ApplicationBarMenuClick(IApplicationBarMenuItem menuItem)
        {
            TraceEntrance();
            DebugTrace(string.Format("ViewModelBase::ApplicationBarButtonClick({0})", menuItem.Text));
            TraceExit();
        }

        public void SaveState(IDictionary<string, object> pageState)
        {
            TraceEntrance();
            PhonePageState state = new PhonePageState(pageState);
            state.Write("IsBusy", IsBusy);
            OnSaveState(state);
            TraceExit();
        }

        public void RestoreState(IDictionary<string, object> pageState)
        {
            TraceEntrance();
            PhonePageState state = new PhonePageState(pageState);
            IsBusy = state.Read<bool>("IsBusy");
            OnRestoreState(state);
            TraceExit();
        }

        public PhoneApplicationBar ApplicationBar
        {
            get; set;
        }
        #endregion

        public bool Valid()
        {
            ClearErrors(null);
            return OnValidate();
        }

        protected virtual bool OnValidate()
        {
            return true;
        }

        public virtual void OnSaveState(PhonePageState state)
        {
            TraceEntrance();
            TraceExit();
        }

        public virtual void OnRestoreState(PhonePageState state)
        {
            TraceEntrance();
            TraceExit();
        }

        protected void ClearErrors(string propertyName)
        {
            TraceEntrance();
            _hasErrors = false;
            if (null == Errors) return;

            if (string.IsNullOrEmpty(propertyName))
            {
                foreach (var key in Errors.Keys)
                {
                    if (null != ErrorsChanged) ErrorsChanged(this, new DataErrorsChangedEventArgs(key));
                }
                Errors.Clear();
            }
            else
            {
                foreach (var key in Errors.Keys)
                {
                    if (key != propertyName)
                    {
                        continue;
                    }
                    Errors.Remove(key);
                    if (null != ErrorsChanged) ErrorsChanged(this, new DataErrorsChangedEventArgs(key));
                }
            }
            TraceExit();
        }

        public bool RaiseDataValidationError(string propertyName)
        {
            return RaiseDataValidationError(propertyName, string.Empty);
        }

        public bool RaiseDataValidationError(string propertyName, string errorMessage)
        {
            TraceEntrance();
            if (null == ErrorsChanged || null == Errors)
            {
                TraceExit();
                return false;
            }

            _hasErrors = true;
            if (!Errors.ContainsKey(propertyName))
            {
                Errors.Add(propertyName, errorMessage);
            }
            else
            {
                Errors[propertyName] = errorMessage;
            }
            DebugTrace("RaiseDataValidationError({0},{1})", propertyName, errorMessage);
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
            TraceExit();
            return false;
        }

        [Conditional("DEBUG")]
        protected void TraceEntrance()
        {
            var stackTrace = new StackTrace();
            var type = GetType();
            string message = string.Format("{0}::{1} : --> {2}", type.Name, stackTrace.GetFrame(1).GetMethod().Name, DateTime.Now.ToString("HH:mm:ss:ffff"));
            Debug.WriteLine(message);
        }

        [Conditional("DEBUG")]
        protected void TraceExit()
        {
            string message = string.Format("<-- {0}", DateTime.Now.ToString("HH:mm:ss:ffff"));
            var stackTrace = new StackTrace();
            var type = GetType();
            message = string.Format("{0}::{1} : {2}", type.Name, stackTrace.GetFrame(1).GetMethod().Name, message);
            Debug.WriteLine(message);
        }

        [Conditional("DEBUG")]
        protected void DebugTrace(string format, params object[] args)
        {
            string message = string.Format(format, args);
            var stackTrace = new StackTrace();
            var type = GetType();
            message = string.Format("{0}::{1} : {2}", type.Name, stackTrace.GetFrame(1).GetMethod().Name, message);
            Debug.WriteLine(message);
        }
    }
}
